package ru.spb.osll.myfit.server.execute;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import ru.spb.osll.myfit.common.Include;
import ru.spb.osll.myfit.common.Page;
import ru.spb.osll.myfit.common.PageElement;
import ru.spb.osll.myfit.common.Table;
import ru.spb.osll.myfit.common.TableCell;
import ru.spb.osll.myfit.common.TableCellResult;
import ru.spb.osll.myfit.common.TableStyle;
import ru.spb.osll.myfit.common.TextParsingTools;

public class XmlPageElementReader {
  /**
   * loger
   */
  private static Logger Log = Logger.getLogger(XmlPageElementReader.class);

  /**
   * name of table tag
   */
  private static String TABLE_TAG = "table";

  /**
   * name of div tag
   */
  private static String DIV_TAG = "div";

  /**
   * name of table row tag
   */
  private static String TABLE_ROW_TAG = "tr";

  /**
   * name of table cell tag
   */
  private static String TABLE_CELL_TAG = "td";

  /**
   * name of span tag
   */
  private static String SPAN_TAG = "span";

  /**
   * name of attribute "class"
   */
  private static String ATRIBUTE_CLASS = "class";

  /**
   * name of text tag
   */
  private static String TEXT_TAG = "#text";

  /**
   * value of attribute "class" for include
   */
  private static String INCLUDE_CLASS_NAME = "included";

  /**
   * value of attribute "class" for included page
   */
  private static String INCLUDE_NODE_CLASS_NAME = "collapsable";

  private static String META_CLASS_NAME = "meta";

  /**
   * name of a tag
   */
  private static String A_TAG = "a";

  /**
   * name of attribute "href"
   */
  private static String ATRIBUTE_HREF = "href";

  /**
   * Xml-header
   */
  public static final String XML_HEADER = "<?xml version=\"1.0\"?>";

  private static final String ERROR = "error";

  /**
   * Value of attribute "class" specifies fail test
   */
  private static final String FAIL = "fail";

  /**
   * Value of attribute "class" specifies pass test
   */
  private static final String PASS = "pass";

  /**
   * This method creates page based on XML tree.
   * 
   * @param list
   *          - element of XML tree for page
   * @param lastTestPage
   *          - page for execution
   * @return page with executed elements
   */
  private static Page createPage(NodeList list, Page lastTestPage) {
    Page page = new Page();

    Queue<Integer> executableElement = new LinkedList<Integer>();
    Vector<PageElement> elements = lastTestPage.getElements();

    int size = elements.size();

    for (int i = 0; i < size; i++) {
      PageElement element = elements.get(i);
      if (isExecutable(element)) {
        executableElement.add(i);
      } else {
        page.insert(element);
      }
    }

    int length = list.getLength();
    for (int i = 0; i < length; i++) {
      Node node = list.item(i);
      String nodeName = node.getNodeName();

      if (nodeName.equals(TABLE_TAG)) {
        Log.debug("find table tag");

        if (!getTableHeader(node).equals(TextParsingTools.IMPORT_HEADER)) {
          Log.debug("find table tag. table tag for testing table");

          Table table = processTable(node);
          page.insertElement(table, executableElement.poll());
        }

        continue;

      }
      if (nodeName.equals(DIV_TAG)) {
        Log.debug("find div tag");
        if (node.getAttributes().getNamedItem(ATRIBUTE_CLASS) != null) {
          String sClass = node.getAttributes().getNamedItem(ATRIBUTE_CLASS).getNodeValue();

          if (sClass.equals(INCLUDE_CLASS_NAME)) {
            Log.debug("div class = included");
            PageElement lastElement = lastTestPage.getElement(executableElement.peek());
            if (lastElement instanceof Include) {
              Include include = processInclude(node, ((Include) lastElement).getPage());
              page.insertElement(include, executableElement.poll());
            }
          }
        }
        continue;
      }

    }
    return page;
  }

  /**
   * it Checks executable element or not. i.e. if element instance of Table or
   * Include , then returns true, else false
   * 
   * @param element
   * @return true if element instance of Table or Include
   */
  private static boolean isExecutable(PageElement element) {
    if (element instanceof Table) {
      return true;
    }
    if (element instanceof Include) {
      return true;
    }
    return false;

  }

  /**
   * 
   * @param node
   *          - XML element for include
   * @param includedPage
   *          for execution
   * @return new Include with executed element
   */
  private static Include processInclude(Node node, Page includedPage) {
    Include include = new Include();
    String includePath = "";
    Page page = null;

    NodeList list = node.getChildNodes();
    int size = list.getLength();

    for (int i = 0; i < size; i++) {
      Node currentNode = list.item(i);
      String nodeName = currentNode.getNodeName();
      if (nodeName.equals(SPAN_TAG)) {
        Log.debug("creating include. find span tag");
        if (currentNode.getAttributes().getNamedItem(ATRIBUTE_CLASS) != null) {
          String attrClass = currentNode.getAttributes().getNamedItem(ATRIBUTE_CLASS).getNodeValue();
          if (attrClass.equals(META_CLASS_NAME)) {
            includePath = processInclidePath(currentNode);
            Log.debug("include path " + includePath);
          }
        }
        continue;
      }

      if (nodeName.equals(DIV_TAG)) {
        Log.debug("creating include. find div tag");
        if (currentNode.getAttributes().getNamedItem(ATRIBUTE_CLASS) != null) {
          String attrClass = currentNode.getAttributes().getNamedItem(ATRIBUTE_CLASS).getNodeValue();
          Log.debug("creating include. find div tag. div contains class");
          if (attrClass.equals(INCLUDE_NODE_CLASS_NAME)) {
            Log.debug("creating include. find div tag. div contains class. class = collapsable");
            page = createPage(currentNode.getChildNodes(), includedPage);

          }
        }
        continue;
      }
    }

    if (page == null) {
      page = new Page();
    }

    include.setPath("." + includePath);
    include.setPage(page);
    return include;
  }

  private static String processInclidePath(Node node) {

    NodeList list = node.getChildNodes();

    int size = list.getLength();
    for (int i = 0; i < size; i++) {
      Node currentNode = list.item(i);
      String nodeName = currentNode.getNodeName();
      if (nodeName.equals(A_TAG)) {
        if (currentNode.getAttributes().getNamedItem(ATRIBUTE_HREF) != null) {
          return currentNode.getAttributes().getNamedItem(ATRIBUTE_HREF).getNodeValue();
        }
      }
    }
    return "";
  }

  static String getTableHeader(Node nodeTable) {
    NodeList nlRows = nodeTable.getChildNodes();

    for (int i = 0; i < nlRows.getLength(); i++) {
      Node nodeRow = nlRows.item(i);
      if (nodeRow.getNodeName().equals("tr")) {
        NodeList nlCells = nodeRow.getChildNodes();
        for (int j = 0; j < nlCells.getLength(); j++) {
          Node nodeCell = nlCells.item(j);
          if (nodeCell.getNodeName().equals("td"))
            return nodeCell.getTextContent();
        }
      }
    }
    return null;
  }

  /**
   * Process table
   * 
   * @param nodeTable
   *          xml-table
   * @param testTable
   *          test table
   */
  static Table processTable(Node nodeTable) {
    Table testTable = new Table(TableStyle.UNKNOWN_FIXTURE);
    NodeList nlRows = nodeTable.getChildNodes();

    int rowCount = nlRows.getLength();
    for (int i = 0; i < rowCount; i++) {
      Node nodeRow = nlRows.item(i);
      if (nodeRow.getNodeName().equals(TABLE_ROW_TAG)) {
        Vector<TableCell> row = processRow(nodeRow);
        testTable.addLine(row);
      }

    }
    return testTable;
  }

  /**
   * Process table row
   * 
   * @param nodeRow
   *          xml-row
   * @param row
   *          test table row
   */
  static Vector<TableCell> processRow(Node nodeRow) {
    Vector<TableCell> row = new Vector<TableCell>();
    NodeList nlCells = nodeRow.getChildNodes();

    int cellCount = nlCells.getLength();
    for (int i = 0; i < cellCount; i++) {
      Node nodeCell = nlCells.item(i);
      if (nodeCell.getNodeName().equals(TABLE_CELL_TAG)) {
        TableCell cell = processCell(nodeCell);
        row.add(cell);
      }
    }
    return row;
  }

  /**
   * Process tale cell
   * 
   * @param nodeCell
   *          xml-cell
   * @param cell
   *          test table cell
   */
  private static TableCell processCell(Node nodeCell) {

    TableCell cell = new TableCell();

    if (nodeCell.getAttributes().getNamedItem(ATRIBUTE_CLASS) != null) {
      String sClass = nodeCell.getAttributes().getNamedItem(ATRIBUTE_CLASS).getNodeValue();
      if (sClass.equals(PASS)) {
        cell.setTestResult(TableCellResult.PASS);
        cell.setText(nodeCell.getTextContent());
      } else if (sClass.equals(FAIL)) {
        processFailCell(nodeCell, cell);
      } else if (sClass.equals(ERROR)) {
        processErrorCell(nodeCell, cell);
      }
    } else {
      cell.setText(nodeCell.getTextContent());
    }
    return cell;
  }

  /**
   * Process error cell
   * 
   * @param nodeCell
   * @param cell
   */
  private static void processErrorCell(Node nodeCell, TableCell cell) {
    cell.setTestResult(TableCellResult.EXCEPTION);

    String sReal = "";
    String sException = "";

    NodeList nlValues = nodeCell.getChildNodes();
    ArrayList<String> values = new ArrayList<String>();

    int size = nlValues.getLength();
    for (int i = 0; i < size; i++) {
      Node nodeValue = nlValues.item(i);
      if (nodeValue.getNodeName().equals(TEXT_TAG)) {
        values.add(nodeValue.getNodeValue());
      }
    }

    if (values.size() == 2) {
      sReal = values.get(0);
      sException = values.get(1);
    }

    cell.setText(sReal);
    cell.setTooltip(sException);
  }

  /**
   * Process fail cell
   * 
   * @param nodeCell
   * @param cell
   */
  private static void processFailCell(Node nodeCell, TableCell cell) {
    cell.setTestResult(TableCellResult.FAIL);

    String sReal = "";
    String sExpected = "";

    NodeList nlValues = nodeCell.getChildNodes();
    ArrayList<String> values = new ArrayList<String>();

    for (int i = 0; i < nlValues.getLength(); i++) {
      Node nodeValue = nlValues.item(i);
      if (nodeValue.getNodeName().equals(TEXT_TAG)) {
        values.add(nodeValue.getNodeValue());
      }
    }

    if (values.size() == 2) {
      sReal = values.get(0);
      sExpected = values.get(1);
    }
    cell.setText(sReal + "(" + sExpected + ")");
  }

  /**
   * Parse FitNesse xml-result
   * 
   * @param sResult
   * @return
   * @throws SAXException
   * @throws IOException
   * @throws ParserConfigurationException
   */
  public static Page parse(String sResult, Page lastTestPage) throws SAXException, IOException, ParserConfigurationException {

    String xmlResult = createXmlByHtml(sResult);
    InputSource in = new InputSource(new ByteArrayInputStream(xmlResult.getBytes()));

    DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
    Document doc = docBuilderFactory.newDocumentBuilder().parse(in);
    NodeList list = doc.getDocumentElement().getChildNodes();

    return createPage(list, lastTestPage);
  }

  public static String createXmlByHtml(String sResult) {
    String xmlResult = sResult;
    while (xmlResult.contains("<hr>")) {
      int startIndex = xmlResult.indexOf("<hr>");
      xmlResult = xmlResult.substring(0, startIndex) + xmlResult.substring(startIndex + 4);
    }
    return XML_HEADER + "<root>" + xmlResult + "</root>";
  }

  public Table parseTable(String sResult) throws SAXException, IOException, ParserConfigurationException {

    String xmlResult = createXmlByHtml(sResult);
    InputSource in = new InputSource(new ByteArrayInputStream(xmlResult.getBytes()));

    DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
    Document doc;
      doc = docBuilderFactory.newDocumentBuilder().parse(in);
      NodeList list = doc.getDocumentElement().getChildNodes();
      int length = list.getLength();
      for (int i = 0; i < length; i++) {
        Node node = list.item(i);
        String nodeName = node.getNodeName();

        if (nodeName.equals(TABLE_TAG)) {
          Log.debug("find table tag");

          if (!getTableHeader(node).equals(TextParsingTools.IMPORT_HEADER)) {
            Log.debug("find table tag. table tag for testing table");

            return processTable(node);
          }
        }

      }
   
    return null;
  }

}
